package com.uobgroup.ews.uam.web;

import java.io.UnsupportedEncodingException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.util.UriUtils;
import org.springframework.web.util.WebUtils;

import com.uobgroup.ews.uam.core.dao.ldap.LDSDaoImpl;
import com.uobgroup.ews.uam.core.domain.CombinationMatrix;
import com.uobgroup.ews.uam.core.domain.CombinationType;
import com.uobgroup.ews.uam.core.domain.Profile;
import com.uobgroup.ews.uam.core.domain.Reference;
import com.uobgroup.ews.uam.core.domain.UAMRole;
import com.uobgroup.ews.uam.core.model.CombinationACLParamsName;
import com.uobgroup.ews.uam.core.model.Group;
import com.uobgroup.ews.uam.core.model.ProfileInfoList;
import com.uobgroup.ews.uam.core.service.AccessToStaffService;
import com.uobgroup.ews.uam.core.service.CombinationMatrixService;
import com.uobgroup.ews.uam.core.service.ProfileService;
import com.uobgroup.ews.uam.core.service.ReferenceService;
import com.uobgroup.ews.uam.core.service.SecurityGroupService;
import com.uobgroup.ews.uam.core.service.UAMRoleService;
import com.uobgroup.ews.uam.core.service.ldap.LDSServiceImpl;
import com.uobgroup.ews.uam.web.dto.SearchCombinationMatrix;
import com.uobgroup.ews.uam.web.dto.SearchReference;
import com.uobgroup.ews.uam.web.dto.SearchRole;
import com.uobgroup.ews.uam.web.validation.ProfileValidator;

@RequestMapping("/profiles")
@Controller
public class ProfileController {

	@Autowired
	ReferenceService referenceService;

	@Autowired
	ProfileService profileService;
	
	@Autowired
	SecurityGroupService securityGroupService;

	@Autowired
	CombinationMatrixService combinationMatrixService;

	@Autowired
	UAMRoleService roleService;

	@Autowired
	AccessToStaffService accessToStaffService;
	
	@Autowired
	ProfileValidator profileValidator;
	
	@Autowired
   private LDSServiceImpl lDsService;
	
	@Autowired
   private ReloadableResourceBundleMessageSource messageSource;
	
	@InitBinder
   protected void InitBinder(WebDataBinder binder){
      binder.setValidator(profileValidator);
   }
	
	@RequestMapping(method = RequestMethod.POST, produces = "text/html")
	public String create(@Valid Profile profile, BindingResult bindingResult,
			Model uiModel, HttpServletRequest httpServletRequest, Principal principal) {
	   updateRelatedInfo(profile);
	   
	   Group group = lDsService.findGroupByBaseDN(profile.getName().trim(), LDSDaoImpl.LDAP_DN_GROUP_PROFILES);
	   if (null != group) {
	      uiModel.addAttribute("messageError", messageSource.getMessage("exist_security_group_in_lds", new String[] {messageSource.getMessage("label_com_uobgroup_ews_uam_core_domain_profile", null,null)}, null));
	   }
		if (bindingResult.hasErrors() || null != group) {
			populateEditForm(uiModel, profile);
			return "profiles/create";
		}
      
		uiModel.asMap().clear();
		profile.setLastModificationUserId(principal.getName());
		profileService.addNewProfile(profile);
		return "redirect:/profiles/view/"
				+ encodeUrlPathSegment(profile.getId().toString(),
						httpServletRequest);
	}
	
	private void updateRelatedInfo(Profile profile){
      if (profile.getSecurityGroup() != null) {
         profile.setSecurityGroup(securityGroupService.findSecurityGroup(profile.getSecurityGroup().getId()));
      }
	   List<UAMRole> roles = profile.getRoles();
	   if(roles != null && roles.size() > 0){
	      for(int i = 0; i < roles.size(); i++){
	         UAMRole role = roles.get(i);
	         role = roleService.findUAMRole(role.getId());
	         roles.set(i, role);
	      }
	   }
	   
	   List<Reference> segments = profile.getSegments();
	   if(segments != null && segments.size() > 0){
	      for(int i = 0; i < segments.size(); i++){
	         Reference segment = segments.get(i);
	         segment = referenceService.findReference(segment.getId());
	         segments.set(i, segment);
	      }
	   }
	   
	   List<CombinationMatrix> matrices = profile.getCombinationMatrices();
	   if(matrices != null && matrices.size() > 0){
	      for(int i = 0; i < matrices.size(); i++){
	         CombinationMatrix matrix = matrices.get(i);
	         matrix = combinationMatrixService.findbyId(matrix.getId());
	         matrices.set(i, matrix);
	      }
	   }
	}

	@RequestMapping(value = "/create", produces = "text/html")
	public String createForm(Model uiModel) {
		populateEditForm(uiModel, new Profile());
		return "profiles/create";
	}

	@RequestMapping(value = "/view/{id}", produces = "text/html")
	public String show(@PathVariable("id") Long id, Model uiModel) {
		uiModel.addAttribute("profile", profileService.findProfileById(id));
		uiModel.addAttribute("canDelete", !profileService.isProfileInUse(id));
		uiModel.addAttribute("itemId", id);
		return "profiles/show";
	}

	@RequestMapping(produces = "text/html")
	public String list(
			@RequestParam(value = "page", required = false) Integer page,
			@RequestParam(value = "size", required = false) Integer size,
			Model uiModel, @ModelAttribute("error") final String error) {
		//if (page != null || size != null) {
			int sizeNo = size == null ? 10 : size.intValue();
			final int firstResult = page == null ? 0 : (page.intValue() - 1)
					* sizeNo;
			uiModel.addAttribute("profiles",
					profileService.findProfiles(firstResult, sizeNo));
			float nrOfPages = (float) profileService.countProfiles() / sizeNo;
			uiModel.addAttribute(
					"maxPages",
					(int) ((nrOfPages > (int) nrOfPages || nrOfPages == 0.0) ? nrOfPages + 1
							: nrOfPages));
			uiModel.addAttribute("deleteMessageError", error);
		/*} else {
			uiModel.addAttribute("profiles", profileService.findAllProfiles());
		}*/
		return "profiles/list";
	}

	@RequestMapping(method = RequestMethod.PUT, produces = "text/html")
	public String update(@Valid Profile profile, BindingResult bindingResult,
			Model uiModel, HttpServletRequest httpServletRequest, Principal principal, final RedirectAttributes redirectAttrs) {
	   updateRelatedInfo(profile);
	   Profile p1 = profileService.findProfileById(profile.getId());
      if (null == p1) {
         redirectAttrs.addFlashAttribute("error", messageSource.getMessage("entity_not_found_single", new String[] {messageSource.getMessage("label_com_uobgroup_ews_uam_core_domain_profile", null, null)}, "", null));
         redirectAttrs.addAttribute("page", "1");
         redirectAttrs.addAttribute("size", "10");
         return "redirect:/profiles";
      }
		if (bindingResult.hasErrors()) {
			populateEditForm(uiModel, profile);
			return "profiles/update";
		}
		uiModel.asMap().clear();
		profile.setLastModificationUserId(principal.getName());
		profileService.updateProfile(profile);
		return "redirect:/profiles/view/"
				+ encodeUrlPathSegment(profile.getId().toString(),
						httpServletRequest);
	}

	@RequestMapping(value = "/update/{id}", produces = "text/html")
	public String updateForm(@PathVariable("id") Long id, Model uiModel, final RedirectAttributes redirectAttrs) {
	   Profile profile = profileService.findProfileById(id);
	   if (null == profile) {
         redirectAttrs.addFlashAttribute("error", messageSource.getMessage("entity_not_found_single", new String[] {messageSource.getMessage("label_com_uobgroup_ews_uam_core_domain_profile", null, null)}, "", null));
         redirectAttrs.addAttribute("page", "1");
         redirectAttrs.addAttribute("size", "10");
         return "redirect:/profiles";
      }
		populateEditForm(uiModel, profileService.findProfileById(id));
		return "profiles/update";
	}

	@RequestMapping(value = "/{id}", method = RequestMethod.DELETE, produces = "text/html")
	public String delete(@PathVariable("id") Long id,
			@RequestParam(value = "page", required = false) Integer page,
			@RequestParam(value = "size", required = false) Integer size,
			Model uiModel, Principal principal, final RedirectAttributes redirectAttrs) {
	   uiModel.asMap().clear();
	   Profile profile = profileService.findProfileById(id);
	   if (null == profile) {
	      redirectAttrs.addFlashAttribute("error", messageSource.getMessage("entity_not_found_single", new String[] {messageSource.getMessage("label_com_uobgroup_ews_uam_core_domain_profile", null, null)}, null));
	   } else {
   	   if(!profileService.isProfileInUse(id)){
   	      profileService.updateUserIdAndDelete(id, principal.getName());
   	   } else{
   	      redirectAttrs.addFlashAttribute("error", messageSource.getMessage("field_cannot_delete", new Object[]{}, null));
   	      redirectAttrs.addAttribute("page", (page == null) ? 1 : page);
   	      redirectAttrs.addAttribute("size", (size == null) ? 10 : size);
   	      return "redirect:/profiles";
   	      /*if (page != null || size != null) {
   	         int sizeNo = size == null ? 10 : size.intValue();
   	         final int firstResult = page == null ? 0 : (page.intValue() - 1)
   	               * sizeNo;
   	         uiModel.addAttribute("profiles",
   	               profileService.findProfiles(firstResult, sizeNo));
   	         float nrOfPages = (float) profileService.countProfiles() / sizeNo;
   	         uiModel.addAttribute(
   	               "maxPages",
   	               (int) ((nrOfPages > (int) nrOfPages || nrOfPages == 0.0) ? nrOfPages + 1
   	                     : nrOfPages));
   	      } else {
   	         uiModel.addAttribute("profiles", profileService.findAllProfiles());
   	      }
   	      return "profiles/list";*/
   	   }
	   }
	   redirectAttrs.addAttribute("page", (page == null) ? "1" : page.toString());
	   redirectAttrs.addAttribute("size", (size == null) ? "10" : size.toString());
      return "redirect:/profiles";
	}

	@RequestMapping(value = "/search/segments", method = RequestMethod.GET, produces = "text/html")
	public String searchSegmentForm(Model uiModel) {
		uiModel.addAttribute("segment", new SearchReference());
		return "profiles/segmentSearch";
	}

	@RequestMapping(value = "/search/segments", method = RequestMethod.POST)
	public String searchSegment(SearchReference reference, Model uiModel) {
		List<Reference> segments = null;
		String name = reference.getSearchReferenceName();
		if (name == null) {
			name = "";
		}
		segments = referenceService.findSegmentReferenceByName(name);
		uiModel.addAttribute("segments", segments);
		return "profiles/segmentSearchResult";
	}

	@RequestMapping(value = "/search/segments/selected", method = RequestMethod.POST)
	public String searchSegment(@Param("idsSelected") String idsSelected,
			Model uiModel) {
		List<Reference> segments = new ArrayList<Reference>();
		if (idsSelected != null && idsSelected.trim().length() > 0) {
			String[] idStrs = idsSelected.split("\\|");
			List<String> processedIds = new ArrayList<String>();
			for (String idStr : idStrs) {
				if(!processedIds.contains(idStr)){
					processedIds.add(idStr);
					if (idStr.trim().length() > 0) {
						Reference reference = referenceService.findReference(Long
								.parseLong(idStr));
						if (reference != null) {
							segments.add(reference);
						}
					}
				}
			}
		}
		uiModel.addAttribute("segments", segments);
		return "profiles/segmentSelected";
	}

	@RequestMapping(value = "/search/segments/exclude", method = RequestMethod.POST)
	public String searchSegmentExclude(
			@Param("idsSelected") String idsSelected,
			@Param("excludedId") String excludedId, Model uiModel) {
		List<Reference> segments = new ArrayList<Reference>();
		if (idsSelected != null && idsSelected.trim().length() > 0) {
			String[] idStrs = idsSelected.split("\\|");
			List<String> processIds = new ArrayList<String>();
			for (String idStr : idStrs) {
				if (!processIds.contains(idStr)) {
					processIds.add(idStr);
					if (idStr.trim().length() > 0 && !idStr.equals(excludedId)) {
						Reference reference = referenceService
								.findReference(Long.parseLong(idStr));
						if (reference != null) {
							segments.add(reference);
						}
					}
				}
			}
		}
		uiModel.addAttribute("segments", segments);
		return "profiles/segmentSelected";
	}

	@RequestMapping(value = "/search/combinations", method = RequestMethod.GET, produces = "text/html")
	public String searchCombinationForm(Model uiModel) {
		uiModel.addAttribute("combination", new SearchCombinationMatrix());
		return "profiles/combinationSearch";
	}

	@RequestMapping(value = "/search/combinations", method = RequestMethod.POST)
	public String searchCombination(SearchCombinationMatrix combination, Model uiModel) {
	   CombinationType combinationType = null;
	   if((combination.getIsCase() && combination.getIsDocument())
	         || (!combination.getIsCase() && !combination.getIsDocument())){
	      combinationType = null;
	   } else{
	      if(combination.getIsCase()){
	         combinationType = CombinationType.CASE;
	      } else{
	         combinationType = CombinationType.DOCUMENT;
	      }
	   }
		List<CombinationMatrix> combinations = combinationMatrixService
				.findByTypeAndDescription(combinationType,
						combination.getSearchDescription());
		uiModel.addAttribute("combinations", combinations);
		return "profiles/combinationSearchResult";
	}

	@RequestMapping(value = "/search/combinations/selected", method = RequestMethod.POST)
	public String searchCombination(@Param("idsSelected") String idsSelected,
			Model uiModel) {
		List<CombinationMatrix> combinations = new ArrayList<CombinationMatrix>();
		if (idsSelected != null && idsSelected.trim().length() > 0) {
			String[] idStrs = idsSelected.split("\\|");
			List<String> processedIds = new ArrayList<String>();
			for (String idStr : idStrs) {
				if (!processedIds.contains(idStr)) {
					processedIds.add(idStr);
					if (idStr.trim().length() > 0) {
						CombinationMatrix combination = combinationMatrixService
								.findbyId(Long.parseLong(idStr));
						if (combination != null) {
							combinations.add(combination);
						}
					}
				}
			}
		}
		uiModel.addAttribute("combinations", combinations);
		return "profiles/combinationSelected";
	}

	@RequestMapping(value = "/search/combinations/exclude", method = RequestMethod.POST)
	public String searchCombinationExclude(
			@Param("idsSelected") String idsSelected,
			@Param("excludedId") String excludedId, Model uiModel) {
		List<CombinationMatrix> combinations = new ArrayList<CombinationMatrix>();
		if (idsSelected != null && idsSelected.trim().length() > 0) {
			String[] idStrs = idsSelected.split("\\|");
			List<String> processIds = new ArrayList<String>();
			for (String idStr : idStrs) {
				if (!processIds.contains(idStr)) {
					processIds.add(idStr);
					if (idStr.trim().length() > 0 && !idStr.equals(excludedId)) {
						CombinationMatrix combination = combinationMatrixService
								.findbyId(Long.parseLong(idStr));
						if (combination != null) {
							combinations.add(combination);
						}
					}
				}
			}
		}
		uiModel.addAttribute("combinations", combinations);
		return "profiles/combinationSelected";
	}

	@RequestMapping(value = "/search/roles", method = RequestMethod.GET, produces = "text/html")
   public String searchRoleForm(Model uiModel) {
      uiModel.addAttribute("role", new SearchRole());
      return "profiles/roleSearch";
   }

   @RequestMapping(value = "/search/roles", method = RequestMethod.POST)
   public String searchRole(SearchRole searchRole, Model uiModel) {
      List<UAMRole> roles = null;
      String name = searchRole.getSearchRoleName();
      if (name == null) {
         name = "";
      }
      roles = roleService.findByName(name);
      uiModel.addAttribute("roles", roles);
      return "profiles/roleSearchResult";
   }

   @RequestMapping(value = "/search/roles/selected", method = RequestMethod.POST)
   public String searchRole(@Param("idsSelected") String idsSelected,
         Model uiModel) {
      List<UAMRole> roles = new ArrayList<UAMRole>();
      if (idsSelected != null && idsSelected.trim().length() > 0) {
         String[] idStrs = idsSelected.split("\\|");
         List<String> processedIds = new ArrayList<String>();
         for (String idStr : idStrs) {
            if(!processedIds.contains(idStr)){
               processedIds.add(idStr);
               if (idStr.trim().length() > 0) {
                  UAMRole role = roleService.findUAMRole(Long.parseLong(idStr));
                  if (role != null) {
                     roles.add(role);
                  }
               }
            }
         }
      }
      uiModel.addAttribute("roles", roles);
      return "profiles/roleSelected";
   }

   @RequestMapping(value = "/search/roles/exclude", method = RequestMethod.POST)
   public String searchRoleExclude(
         @Param("idsSelected") String idsSelected,
         @Param("excludedId") String excludedId, Model uiModel) {
      List<UAMRole> roles = new ArrayList<UAMRole>();
      if (idsSelected != null && idsSelected.trim().length() > 0) {
         String[] idStrs = idsSelected.split("\\|");
         List<String> processIds = new ArrayList<String>();
         for (String idStr : idStrs) {
            if (!processIds.contains(idStr)) {
               processIds.add(idStr);
               if (idStr.trim().length() > 0 && !idStr.equals(excludedId)) {
                  UAMRole role = roleService.findUAMRole(Long.parseLong(idStr));
                  if (role != null) {
                     roles.add(role);
                  }
               }
            }
         }
      }
      uiModel.addAttribute("roles", roles);
      return "profiles/roleSelected";
   }
	
	@RequestMapping(value = "/segments/remove/{id}", method = RequestMethod.DELETE, produces = "text/html")
	public String removeSegment(@PathVariable("id") Long id,
			@RequestParam(value = "page", required = false) Integer page,
			@RequestParam(value = "size", required = false) Integer size,
			Model uiModel) {
		return "profiles/segmentSelected";
	}

	void populateEditForm(Model uiModel, Profile profile) {
		uiModel.addAttribute("profile", profile);
		uiModel.addAttribute("accesstostaffs",
				accessToStaffService.findAllAccessToStaffs());

	}

	@RequestMapping(method = RequestMethod.GET, value = "/getProfileInfo", headers = "Accept=application/json")
   public @ResponseBody
   ProfileInfoList getProfileInfo(
         @RequestParam(value = "segment", required = true) String segment,
         @RequestParam(value = "doctype", required = false) String doctype,
         @RequestParam(value = "country", required = true) String country,
         @RequestParam(value = "processingcenter", required = false) String processingcenter,
         @RequestParam(value = "transactiontype", required = false) String transactiontype,
         @RequestParam(value = "producttype", required = false) String producttype,
         @RequestParam(value = "userlocation", required = false) String userlocation) {

	   List<String> segmentName = buildSegmentNames(segment);
      HashMap<String, List<String>> params = buildCombinationParams(doctype, country, processingcenter,
            transactiontype, producttype, userlocation);

      return profileService.getProfileInfoList(segmentName, params);
   }
	
	List<String> buildSegmentNames(String segment){
	   List<String> lstName = new ArrayList<String>();
	   String[] paramArr = segment.split(",");
      int len = paramArr.length;
      for (int i = 0; i < len; i++) {
         lstName.add(paramArr[i]);
      }
	   return lstName;
	}
	
	private HashMap<String, List<String>> buildCombinationParams(String docType, String country,
         String processingCenter, String transactionType, String productType, String userLocation) {
      HashMap<String, List<String>> params = new HashMap<String, List<String>>();

      buildCombiParam(CombinationACLParamsName.DOC_TYPE, docType, params);
      buildCombiParam(CombinationACLParamsName.COUNTRY, country, params);
      buildCombiParam(CombinationACLParamsName.PROCESSING_CENTER, processingCenter, params);
      buildCombiParam(CombinationACLParamsName.TRANSACTION_TYPE, transactionType, params);
      buildCombiParam(CombinationACLParamsName.PRODUCT_TYPE, productType, params);
      buildCombiParam(CombinationACLParamsName.USER_LOCATION, userLocation, params);

      return params;
   }
	
	private void buildCombiParam(String key, String param, HashMap<String, List<String>> params) {
      int len = 0;
      if (param != null) {
         List<String> values = new ArrayList<String>();
         String[] paramArr = param.split(",");
         len = paramArr.length;
         for (int i = 0; i < len; i++) {
            values.add(paramArr[i]);
         }
         params.put(key, values);
      }
   }
	
	String encodeUrlPathSegment(String pathSegment,
			HttpServletRequest httpServletRequest) {
		String enc = httpServletRequest.getCharacterEncoding();
		if (enc == null) {
			enc = WebUtils.DEFAULT_CHARACTER_ENCODING;
		}
		try {
			pathSegment = UriUtils.encodePathSegment(pathSegment, enc);
		} catch (UnsupportedEncodingException uee) {
		}
		return pathSegment;
	}
	
	// work-around : when form has validation error, click submit twice -> http method auto change to DELETE???
	/*@RequestMapping(method = RequestMethod.DELETE, produces = "text/html")
   public String del(@Valid Profile profile, BindingResult bindingResult,
         Model uiModel, HttpServletRequest httpServletRequest, Principal principal) {
      return create(profile, bindingResult, uiModel, httpServletRequest, principal);
   }*/
}
